using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Xml;
using Web.Ajax.Logging;
using System.Runtime.Serialization;
using System.Linq;
using System.Linq.Expressions;

namespace Web.Ajax
{
	public class Ajax : Attribute
	{
		public enum AjaxTarget
		{
			Div,
			Function
		}
		public AjaxTarget Target = AjaxTarget.Function;
        public string Namespace;
        public string Function;

        public string Name
        {
            get
            {
                if (Namespace == null)
                    return Function;
                return Namespace + "." + Function;
            }
            set
            {
                if (value == null)
                    return;
                string[] parts = value.Split('.');
                if (parts.Length > 1)
                {
                    Namespace = "";
                    for (int i = 0; i < parts.Length - 1; i++)
                    {
                        if(i>0)
                            Namespace += ".";
                        Namespace += parts[i];
                    }
                }
                Function = parts[parts.Length - 1];
            }
        }

		
		#region RegisterAjaxMethods
		public static void RegisterAjaxMethods(Control Control)
		{
			RegisterAjaxMethods(Control,Control.GetType());
		}

		public static void RenderAjaxScript(object sender, EventArgs e)
		{
			var c = sender as Control;
			if (c == null)
				return;
			var l = c.Page.Items["AjaxMethods"] as List<MethodInfo>;
			if (l == null)
				return;

			var script = new StringBuilder("<script type=\"text/javascript\">");
			int Count = 0;
			foreach(var m in l)
			{
				Ajax ajaxAttr;
				object[] Attributes = m.GetCustomAttributes(typeof(Ajax), true);
				if (Attributes != null && Attributes.Length > 0)
					ajaxAttr = (Ajax)Attributes[0];
				else
					ajaxAttr = new Ajax();
					
				if (ajaxAttr.Namespace == null)
					ajaxAttr.Namespace = m.DeclaringType.FullName;
				if (ajaxAttr.Function == null)
					ajaxAttr.Function = m.Name;

				Count++;
				script.Append("\nNamespace('" + ajaxAttr.Namespace + "')['"+ajaxAttr.Function+"']=function(");
				ParameterInfo[] Parameters = m.GetParameters();
				for (int j = 0; j < Parameters.Length; j++)
				{
					script.Append(Parameters[j].Name);
					script.Append(",");
				}
				script.Append("OnCompleted)\n{\n");
				
				var pl=new StringBuilder("[\n");
				for (int j = 0; j < Parameters.Length; j++)
				{
					pl.Append("\t\tnew Parameter('" + Parameters[j].Name + "'," + Parameters[j].Name + ",'" + Parameters[j].ParameterType.AssemblyQualifiedName + "')" + ((j < (Parameters.Length - 1)) ? "," : "") + "\n");
				}
				pl.Append("\t]");

				script.Append("AjaxMethod(\n");
				script.Append("\tlocation.href,\n\t'" + m.DeclaringType.AssemblyQualifiedName + "',\n\t'" + m.Name + "',\n\t"+pl.ToString()+",\n\tOnCompleted);\n");
				script.Append("}");				
			}
			script.Append("\n</script>");
			if (Count > 0)
				c.Page.ClientScript.RegisterClientScriptBlock(sender.GetType(), "AjaxScript_" + sender.GetType().Name, script.ToString());
		}
		public static void RegisterMethod(Control c, MethodInfo m)
		{
			if (c == null || m==null)
				return;
			var l=c.Page.Items["AjaxMethods"] as List<MethodInfo>;
			if (l == null)
			{
				l = new List<MethodInfo>();
				c.Page.Items["AjaxMethods"] = l;
			}
			if(!l.Contains(m))
				l.Add(m);
			c.Page.PreRender -= RenderAjaxScript;			
			c.Page.PreRender += RenderAjaxScript;
		}
		public static void RegisterMethods(Control c, params Expression<Action>[] mcl)
		{
			RegisterMethods(c, (LambdaExpression[])mcl);
		}
		public static void RegisterMethods<T>(Control c, params Expression<Action<T>>[] mcl)
		{
			RegisterMethods(c, (LambdaExpression[])mcl);
		}
		public static void RegisterMethods<T, TResult>(Control c, params Expression<Func<T, TResult>>[] mcl)
		{
			RegisterMethods(c, (LambdaExpression[])mcl);
		}
		public static void RegisterMethods(Control c, params LambdaExpression[] mcl)
		{
			foreach (var mc in mcl)
			{
				MethodCallExpression outermostExpression = mc.Body as MethodCallExpression;
				if (outermostExpression != null)
					RegisterMethod(c, outermostExpression.Method);
			}
		}

		public static void RegisterAjaxMethods(Control Control,Type Type)
		{
			MethodInfo[] Methods = Type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);

			if (Type.IsInterface)
				Methods = Type.GetMethods();

			if (Methods.Length == 0)
				return;			

			foreach (var m in Methods)
			{
				object[] Attributes = m.GetCustomAttributes(typeof(Ajax), true);
				if (Attributes != null && Attributes.Length > 0)
					RegisterMethod(Control, m);
			}
		}		
		#endregion




		#region CallAjaxMethods
		public static void CallAjaxMethods(Control Control)
		{
			HttpRequest Request = HttpContext.Current.Request;
			HttpResponse Response = HttpContext.Current.Response;
			string xml=null;
			object retVal=null;
			if (IsAjaxRequest(Request))
			{
				int length = (int)Request.InputStream.Length;
				byte[] bytes = new byte[length];
				Request.InputStream.Read(bytes, 0, length);
				xml = Encoding.UTF8.GetString(bytes);
				if (string.IsNullOrWhiteSpace(xml))
					return;
			}
			else
				return;
			DateTime startTime = DateTime.Now;
			Response.Clear();
			Ajax ajax = null;
			XmlDocument XmlDocument = new XmlDocument();
			XmlDocument.LoadXml(xml);
			XmlElement AjaxMethodElement = XmlDocument.DocumentElement;
			if (AjaxMethodElement != null)
			{
				string TypeName = AjaxMethodElement.Attributes["Type"].Value;
				string MethodName = AjaxMethodElement.Attributes["Name"].Value;
				Type Type=null;
				MethodInfo Method=null;
				if (TypeName != null)
					Type = Type.GetType(TypeName);
				if(Type!=null)
					Method=Type.GetMethod(MethodName);
				List<object> Parameters=new List<object>();
				try
				{
					for (int i = 0; i < AjaxMethodElement.ChildNodes.Count; i++)
					{
						XmlNode Parameter = AjaxMethodElement.ChildNodes[i];
						if (Parameter.Name == "P")
							Parameters.Add(Json.ConvertFromJson(Type.GetType(Parameter.Attributes["Type"].Value), Parameter.InnerText));
					}
				}
				catch(Exception e)
				{
					Log.Error(e);
					Response.AddHeader("Error", "InvalidParameter");
					Response.Write(e.Message);
					Response.End();
					return;
				}
				if (Method != null)
				{
					try
					{
						if (Type.IsInterface)
						{
							var o=InversionOfControl.Container.Instance.GetImplementation(Type);
							retVal = Method.Invoke(o, Parameters.ToArray());
						}
						else
						{
							retVal = Method.Invoke(null, Parameters.ToArray());
						}
					}
					catch(Exception e)
					{
						Log.Error(e);
						Response.AddHeader("Error","MethodInvocation");
						Response.Write(e.InnerException.Message);
						Response.End();
						return;
					}
					object[] attributes = Method.GetCustomAttributes(typeof(Ajax), true);
					if (attributes.Length > 0)
						ajax = (Ajax)attributes[0];
				}
				else
				{
					Log.Error("Invalid Method: "+TypeName+"."+MethodName);
					Response.AddHeader("Error", "InvalidMethod");
					Response.Write(TypeName + "." + MethodName);
					Response.End();
					return;
				}
			}
			string retJson="";
			try
			{
				if (ajax != null && ajax.Target == AjaxTarget.Div)
				{
					if(retVal is string)
						retJson = (string)retVal;
					else 
						retJson = Json.ConvertToJson(retVal);
				}
				else
					retJson = Json.ConvertToJson(retVal);
			}
			catch(Exception e)
			{
				Log.Error(e);
				Response.AddHeader("Error", "JsonConversion");
				Response.Write(e.Message);
				Response.End();
				return;
			}
			Response.AddHeader("MethodCallTime",DateTime.Now.Subtract(startTime).TotalSeconds.ToString("N3"));
			Response.Write(retJson);
			Response.End();
		}
		#endregion

		#region IsAjaxRequest
		/// <summary>
		/// Determines if a Request is an Ajax Request.
		/// </summary>
		/// <param name="Request"></param>
		/// <returns></returns>
		public static bool IsAjaxRequest(HttpRequest Request)
		{
			if (Request.Headers["RequestType"] == "AjaxMethod")
				return true;
			return false;
		} 
		#endregion
	}


	//*/
	[DataContract]
	public class Range<T> 
	{
		[DataMember]
		public bool Success;
		[DataMember]
		public string Message;
		[DataMember]
		public T Data;


		public string OnComplete;

		public string Error;

		public Range<T> Fail(Exception e)
		{
			Success = false;
			if (e != null)
				Message = e.Message;
			return this;
		}

		public Range<T> Succeed()
		{
			Success = true;
			return this;
		}

		public Range<T> Succeed(T Data)
		{
			this.Data = Data;
			return Succeed();
		}

		public static Range<T> Get(Func<T> r)
		{
			var res = new Range<T>();
			try
			{
				return res.Succeed(r());
			}
			catch (Exception e)
			{
				return res.Fail(e);
			}
		}

		public static T2 Get<T2>(Func<T> r) where T2 : Range<T>, new()
		{
			var res = new T2();
			try
			{
				return (T2)res.Succeed(r());
			}
			catch (Exception e)
			{
				return (T2)res.Fail(e);
			}
		}
	}

	
	public class SimpleResponse : Range<object>
	{		
		public static SimpleResponse Get(Func<object> r)
		{
			return Range<object>.Get<SimpleResponse>(r);
		}		
	}

	public class ServiceResponse<T> : Range<T>
	{
	}

	public class ServiceResponse : Range<object>
	{
		public static ServiceResponse Get(Func<object> r)
		{
			return Range<object>.Get<ServiceResponse>(r);
		}
	}


	/*
    public class SimpleResponse
    {
        public bool Success;
        public string Message;
        public object Data;
		public string OnComplete;
		public string Error;

		public SimpleResponse Fail(Exception e)
		{
			Success = false;
			if(e!=null)
				Message = e.Message;
			return this;
		}

		public SimpleResponse Succeed()
		{
			Success = true;
			return this;
		}

		public SimpleResponse Succeed(object Data)
		{
			this.Data = Data;
			return Succeed();
		}

		public static SimpleResponse Get(Func<object> r)
		{
			var res = new SimpleResponse();
			try
			{
				return res.Succeed(r());
			}
			catch (Exception e)
			{
				return res.Fail(e);
			}
		}		
    }
	//*/
}
